#include <boost/bind.hpp>

#ifdef LINUX
#include <sys/ioctl.h>
#include <termios.h>
#endif

#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "serial.h"

SerialCommunication::SerialCommunication(long p_lBaud)
  : m_bWantRun(true),
  m_iFD(-1), m_lBaud(p_lBaud),
  m_pDevice(NULL), m_pError(NULL),
  m_ptReceive(NULL),	m_ptSend(NULL)
{
}

SerialCommunication::~SerialCommunication()
{
  if (m_iFD>0)
    closeConnection();

  if (m_pDevice!=NULL)
    free(m_pDevice);
  if (m_pError)
    free(m_pError);
}

bool SerialCommunication::setDevice(char const *p_pDevice)
{
  if (p_pDevice!=NULL)
  {
    if (m_pDevice!=NULL)
      free(m_pDevice);
    m_pDevice=strdup(p_pDevice);
    return true;
  }
  return false;
}

int SerialCommunication::openConnection()
{
#ifdef LINUX
  struct termios tio;
  //char id, value;
  //long count=0;

  m_iFD=open(m_pDevice, O_NONBLOCK | O_RDWR | O_NOCTTY );
  if (m_iFD<1)
  {
    printf("ERROR - could not open the connection!\n");
    printf("errno : %i\n", errno);
    perror("perror: ");
    return -1;
  }
  tcgetattr(m_iFD, &tio);
  // TODO 01 set the correct BAUD-rate
  tio.c_cflag=B9600|CS8|CLOCAL|CREAD;
  tio.c_iflag=IGNPAR;
  tio.c_oflag=0;
  tio.c_lflag=0;
  tcflush(m_iFD, TCIFLUSH);
  tcsetattr(m_iFD, TCSANOW, &tio);

  m_ptSend=new boost::thread(boost::bind(&SerialCommunication::writeThread, this));
  m_ptReceive=new boost::thread(boost::bind(&SerialCommunication::readThread, this));
#endif
  m_bWantRun=true;
  return 1;
}

int SerialCommunication::closeConnection()
{
  fprintf(stderr,"stopping threads ....\n");
  // inform the Threads that we want to stop
  {
    scoped_lock lock1(m_syncWrite);
    scoped_lock lock2(m_syncRead);
    m_bWantRun=false;
  }
  m_cSendReady.notify_all();
  m_cReceiveReady.notify_all();

  // wait for the threads
  if (m_ptSend!=NULL)
  {
    m_ptSend->join();
    fprintf(stderr,"Send-Thread joined\n");
  }
  if (m_ptReceive!=NULL)
  {
    m_ptReceive->join();
    fprintf(stderr,"Receive-Thread joined\n");
  }

  // close the connection
  if (m_iFD>0)
    close(m_iFD);
  m_iFD=0;
  return 1;
}

int SerialCommunication::sendByte(unsigned char p_cByte)
{
  scoped_lock lock(m_syncOut);
  m_dOut.push_back(p_cByte);
  m_cSendReady.notify_one();
  return 1;
}

int SerialCommunication::getByte(unsigned char *p_cByte)
{
  scoped_lock lock(m_syncIn);
  if (m_dIn.size()<1)
    return 0;
  *p_cByte=m_dIn.front();
  m_dIn.pop_front();
  return 1;
}

int SerialCommunication::peekByte(unsigned char *p_cByte)
{
  scoped_lock lock(m_syncIn);
  if (m_dIn.size()<1)
    return 0;
  *p_cByte=m_dIn.front();
  return 1;
}

void SerialCommunication::writeThread()
{
  char t;
  while (m_bWantRun)
  {
    scoped_lock lock(m_syncWrite);
    m_cSendReady.wait(lock);
    while (m_dOut.size()>0)
    {
      // TODO 01 wait until bytes can be send again
      scoped_lock lock2(m_syncOut);
      t=m_dOut.front();
      {
        scoped_lock lock3(m_syncAccess);
        if (write(m_iFD, &t, 1)<1)
        {
          handleError(__LINE__, "SERCOM: ERROR - failed to write a byte.");
        } else
          m_dOut.pop_front();
      }
    }
  }
}

void SerialCommunication::readThread()
{
#ifdef LINUX
  unsigned char t;
  int  result;
  unsigned long dataAvailable=0;
  fd_set comset;
  timeval timeVal;

  while (m_bWantRun)
  {
    scoped_lock lock(m_syncRead);
    dataAvailable=0;
    if (-1==ioctl(m_iFD, FIONREAD, &dataAvailable))
    {
      handleError(__LINE__, "SERCOM: ERROR - ioctl failed");
      perror("SERCOM");
      sleep(1);
      continue;
    }

    // no data available, wait for some new to arrive
    if(!dataAvailable)
    {
      FD_ZERO(&comset);
      FD_SET(m_iFD, &comset);

      timeVal.tv_sec=1;
      timeVal.tv_usec=0;

      result=select(m_iFD+1, &comset, NULL, NULL, &timeVal);
      continue;
    }

    scoped_lock lock2(m_syncAccess);
    while(dataAvailable>0)
    {
      if (read(m_iFD, &t, 1)>0)
      {
        m_dIn.push_back(t);
        fprintf(stderr, "received: %03i = %02X\n", t,t);
        --dataAvailable;
        // call the handler in order to inform about new data
        if (!dataAvailable)
        {
          handleReceivedBytes();
        }
      } else
      {
        handleError(__LINE__, "SERCOM: reading failed");
        break;
      }
      //m_cReceiveReady.wait(lock);
    }
  }
#endif
}
